﻿using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ReportGenerator.BL;

namespace Sustainalytics.GCCS.CompanyReportSyncStep
{
    public class GCCSReportBulkDataReader
    {
        private readonly CancellationToken _cancelToken;
        private readonly int _maxDegreeOfParallelism;
        private readonly LogAggregator _logAggregator;

        private readonly string _clientDwUrl;
        private int _skip;
        private readonly int _bulkSize;

        public GCCSReportBulkDataReader(int bulkSize, string clientDwUrl, CancellationToken cancelToken, int maxDegreeOfParallelism = -1)
        {
            _clientDwUrl = clientDwUrl;

            _skip = 0;
            _bulkSize = bulkSize;

            _cancelToken = cancelToken;
            _maxDegreeOfParallelism = maxDegreeOfParallelism;

            if (_maxDegreeOfParallelism < 1)
                _maxDegreeOfParallelism = ParallelHelper.GetMaxDegreeOfParallelism();
        }

        public GCCSReportBulkDataReader(int bulkSize, string clientDwUrl, LogAggregator logAggregator, CancellationToken cancelToken, int maxDegreeOfParallelism = -1)
        {
            _clientDwUrl = clientDwUrl;
            _cancelToken = cancelToken;
            _logAggregator = logAggregator;
            _maxDegreeOfParallelism = maxDegreeOfParallelism;
            _bulkSize = bulkSize;
        }

        public List<GccsReportData> NextBulk(List<Guid> companyIds = null)
        {
            GCCurrentQuarter currentQuarter;

            IEnumerable<Guid> ids;
            IDictionary<Guid, CorporateData> corporateDataDic;
            IDictionary<Guid, GCOverallCompanySummary> overallCompanySummaryDic = null;

            IDictionary<Guid, List<GCPrincipleSummaryExtended>> watchlistPrinciplesDictionary = new Dictionary<Guid, List<GCPrincipleSummaryExtended>>();
            IDictionary<Guid, List<GCPrincipleSummaryExtended>> nonCompliantPrinciplesDictionary = new Dictionary<Guid, List<GCPrincipleSummaryExtended>>();

            IDictionary<Guid, List<GCPrincipleEventIndicatorExtended>> watchlistPrincipleEventIndicatorsDictionary = new Dictionary<Guid, List<GCPrincipleEventIndicatorExtended>>();
            IDictionary<Guid, List<GCPrincipleEventIndicatorExtended>> nonCompliantPrincipleEventIndicatorsDictionary = new Dictionary<Guid, List<GCPrincipleEventIndicatorExtended>>();

            IDictionary<Guid, List<GCSource>> sourcesDictionary = new Dictionary<Guid, List<GCSource>>();

            var currentQuarterRep = new RepositoryCollection<GCCurrentQuarter>(_clientDwUrl);
            var corporateDataRep = new RepositoryCollection<CorporateData>(_clientDwUrl);
            var result = new ConcurrentBag<GccsReportData>();

            IList<CorporateData> globalCompactResearchState;

            if (companyIds == null || !companyIds.Any())
            {
                globalCompactResearchState = corporateDataRep.ReadWhere(cd => cd.GccsResearchState.ResearchState == ResearchState.ResearchAvailable &&
                                                                              cd.GccsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA)).Skip(_skip).Take(_bulkSize).ToList();
            }
            else
            {
                globalCompactResearchState = corporateDataRep.ReadWhere(cd => companyIds.Contains(cd.Id) && 
                cd.GccsResearchState.ResearchState == ResearchState.ResearchAvailable &&
                cd.GccsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA)).Skip(_skip).Take(_bulkSize).ToList();
            }

            if (globalCompactResearchState == null || !globalCompactResearchState.Any()) return result.ToList();
            
            currentQuarter = currentQuarterRep.ReadAll().FirstOrDefault();
                
            corporateDataDic = globalCompactResearchState.ToDictionary(x => x.Id);

            ids = corporateDataDic.Keys.OrderBy(x => x);
                
            _skip += _bulkSize;

            var readActions = new List<Action>();
            readActions.Add(() =>
            {
                var overallCompanySummaryRepo = new RepositoryCollection<GCOverallCompanySummary>(_clientDwUrl);
                overallCompanySummaryDic =
                    overallCompanySummaryRepo.ReadbyIds(ids)
                        .ToDictionary(a => a.Key, a => a.Value);
            });
            readActions.Add(() =>
            {
                var rep = new RepositoryCollection<GCPrincipleSummaryExtended>(_clientDwUrl);
                var principles = rep.ReadAllWhere(p => ids.Contains(p.CompanyId) && p.PrincipleAssessment == GCAssessment.Watchlist).ToList();
                watchlistPrinciplesDictionary = principles.GroupBy(s => s.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
            });
            readActions.Add(() =>
            {
                var rep = new RepositoryCollection<GCPrincipleSummaryExtended>(_clientDwUrl);
                var principles = rep.ReadAllWhere(p => ids.Contains(p.CompanyId) && p.PrincipleAssessment == GCAssessment.NonCompliant).ToList();
                nonCompliantPrinciplesDictionary = principles.GroupBy(s => s.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
            });

            readActions.Add(() =>
            {
                var repo = new RepositoryCollection<GCPrincipleEventIndicatorExtended>(_clientDwUrl);
                var principleEventIndicators = repo.ReadAllWhere(p => ids.Contains(p.CompanyId) && p.EventIndicatorAssessment == GCAssessment.Watchlist).ToList();
                watchlistPrincipleEventIndicatorsDictionary = principleEventIndicators.GroupBy(s => s.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
            });
            readActions.Add(() =>
            {
                var repo = new RepositoryCollection<GCPrincipleEventIndicatorExtended>(_clientDwUrl);
                var principleEventIndicators = repo.ReadAllWhere(p => ids.Contains(p.CompanyId) && p.EventIndicatorAssessment == GCAssessment.NonCompliant).ToList();
                nonCompliantPrincipleEventIndicatorsDictionary = principleEventIndicators.GroupBy(s => s.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
            });

            readActions.Add(() =>
            {
                var sourcesRepo = new RepositoryCollection<GCSource>(_clientDwUrl);
                var sources = sourcesRepo.ReadAllWhere(s => ids.Contains(s.CompanyId));
                sourcesDictionary = sources.GroupBy(s => s.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
            });

            var parallelRunOk = readActions.RunInParallel(_cancelToken, _maxDegreeOfParallelism);

            if (parallelRunOk)
            {
                var exceptions = new ConcurrentQueue<Exception>();
                    
                var options = new ParallelOptions() 
                { 
                    CancellationToken = _cancelToken, 
                    MaxDegreeOfParallelism = _maxDegreeOfParallelism 
                };

                Parallel.ForEach(ids, options, (Guid id) =>
                {
                    try
                    {

                        if (overallCompanySummaryDic.ContainsKey(id))
                        {
                            var watchlistReportData = new GccsReportData
                            {
                                CurrentQuarter = currentQuarter,

                                CompanyId = id,
                                Assessment = GCAssessment.Watchlist,
                                OverallCompanySummary = overallCompanySummaryDic[id],
                                Principles = watchlistPrinciplesDictionary.ContainsKey(id)
                                    ? watchlistPrinciplesDictionary[id]
                                    : new List<GCPrincipleSummaryExtended>(),
                                PrincipleEventIndicators =
                                    watchlistPrincipleEventIndicatorsDictionary.ContainsKey(id)
                                        ? watchlistPrincipleEventIndicatorsDictionary[id]
                                        : new List<GCPrincipleEventIndicatorExtended>(),
                                Sources = sourcesDictionary.ContainsKey(id)
                                    ? sourcesDictionary[id]
                                    : new List<GCSource>()
                            };

                            result.Add(watchlistReportData);

                            var nonCompliantReportData = new GccsReportData
                            {
                                CurrentQuarter = currentQuarter,

                                CompanyId = id,
                                Assessment = GCAssessment.NonCompliant,
                                OverallCompanySummary = overallCompanySummaryDic[id],

                                Principles = nonCompliantPrinciplesDictionary.ContainsKey(id)
                                    ? nonCompliantPrinciplesDictionary[id]
                                    : new List<GCPrincipleSummaryExtended>(),
                                PrincipleEventIndicators =
                                    nonCompliantPrincipleEventIndicatorsDictionary.ContainsKey(id)
                                        ? nonCompliantPrincipleEventIndicatorsDictionary[id]
                                        : new List<GCPrincipleEventIndicatorExtended>(),
                                Sources = sourcesDictionary.ContainsKey(id)
                                    ? sourcesDictionary[id]
                                    : new List<GCSource>()
                            };

                            result.Add(nonCompliantReportData);
                        }
                    }
                    catch (Exception ex)
                    {
                        exceptions.Enqueue(ex);
                    }
                });

                if (exceptions.Count > 0)
                {
                    foreach (var ex in exceptions)
                        _logAggregator.Log(ex.ToString());

                    var aggex = new AggregateException(exceptions);
                    throw aggex.Flatten(); 
                }
            }

            return result.ToList();
        }
    }
}
